package com.fmy.mcsecurity.token.impl.jdbc;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.fmy.mcsecurity.commons.tools.SecurityTool;
import com.fmy.mcsecurity.login.service.McUserDetails;
import com.fmy.mcsecurity.token.model.RefreshStorage;
import com.fmy.mcsecurity.token.model.SecurityToken;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.core.Authentication;

import java.io.ByteArrayOutputStream;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author Mr.fmy
 * @version 1.0.0
 * @ClassName JdbcLoginService
 * @Description TODO jdbc登录服务类
 * @createTime 2020/4/14 13:43
 */
@AllArgsConstructor
@Slf4j
public class JdbcLoginService {

    private final JdbcTemplate jdbcTemplate;

    /**
     * 根据token获取其对应的user_key
     *
     * @param token token值
     * @return user_key
     */
    public String getByTokenToKey(String token) {
        String userKey = JetCacheJdbcKit.getToken().get(token);
        if (StrUtil.isNotBlank(userKey)) {
            return userKey;
        }
        String sql = "select user_key from tb_login_token where token = ? and expire_time > ?";
        try {
            userKey = jdbcTemplate.queryForObject(sql, new Object[]{token, LocalDateTime.now()}, String.class);
            JetCacheJdbcKit.getToken().put(token, userKey);
            return userKey;
        } catch (EmptyResultDataAccessException e) {
            return null;
        }
    }

    /**
     * 根据userKey获取info信息
     *
     * @param userKey 用户唯一标识
     * @return info 流
     */
    public byte[] getByUserToInfo(String userKey) {
        byte[] bytes = JetCacheJdbcKit.getBytes().get(userKey);
        if (bytes != null) {
            return bytes;
        }
        String sql = "select info from tb_login_token where user_key = ?";
        try {
            bytes = jdbcTemplate.queryForObject(sql, new Object[]{userKey}, byte[].class);
            JetCacheJdbcKit.getBytes().put(userKey, bytes);
            return bytes;
        } catch (EmptyResultDataAccessException e) {
            return null;
        }
    }

    public SecurityToken getUserKeyToSecurityStorage(String userKey) {
        String sql = "select * from tb_login_token where user_key = ? and expire_time > ?";
        try {
            Map<String, Object> map = jdbcTemplate.queryForMap(sql, new Object[]{userKey, LocalDateTime.now()});
            SecurityToken token = new SecurityToken();
            token.setToken(map.get("token").toString())
                    .setRefreshToken(map.get("refresh_token").toString())
                    .setTimeUnit(Enum.valueOf(TimeUnit.class, map.get("time_unit").toString()))
                    .setTokenExpire(Long.valueOf(map.get("token_expire").toString()))
                    .setRefreshTokenExpire(Long.valueOf(map.get("refresh_token_expire").toString()))
                    .setLoginTime(DateUtil.toLocalDateTime((Timestamp) map.get("login_time")));
            return token;
        } catch (EmptyResultDataAccessException e) {
            return null;
        }
    }

    public RefreshStorage getRefreshTokenToStorage(String refreshToken) {
        String sql = "select user_id as userId,client,`type` from tb_login_token where refresh_token = ? and refresh_expire_time > ?";
        try {
            Map<String, Object> map = jdbcTemplate.queryForMap(sql, new Object[]{refreshToken, LocalDateTime.now()});
            RefreshStorage storage = new RefreshStorage();
            storage.setUserId(map.get("userId").toString())
                    .setType(map.get("type").toString())
                    .setClient(map.get("client").toString());
            return storage;
        } catch (EmptyResultDataAccessException e) {
            return null;
        }
    }

    /**
     * 储存登录信息
     *
     * @param type           登录类型
     * @param userKey        登录用户key值
     * @param authentication 授权信息
     * @param token          SecurityToken 实体
     * @return 是否成功
     */
    public boolean save(String client, String type, String userKey, Authentication authentication, SecurityToken token) {
        String sql = "insert into tb_login_token(`user_id`, `user_key`, `token_expire`, `token`, `refresh_token`, `refresh_token_expire`," +
                " `create_time`, `expire_time`, `refresh_expire_time`,`client`, `type`, `time_unit`,`login_time`,`info`)" +
                "values(?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
        McUserDetails details = (McUserDetails) authentication.getPrincipal();
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        IoUtil.writeObjects(outputStream, true, authentication);
        byte[] bytes = outputStream.toByteArray();
        LocalDateTime now = LocalDateTime.now();
        int update = jdbcTemplate.update(sql, details.userId(), userKey, token.getTokenExpire(), token.getToken(), token.getRefreshToken(), token.getRefreshTokenExpire(),
                now, SecurityTool.plus(now, token.getTokenExpire(), token.getTimeUnit()), SecurityTool.plus(now, token.getRefreshTokenExpire(), token.getTimeUnit()), client,
                type, token.getTimeUnit().name(), token.getLoginTime(), bytes);
        if (update > 0) {
            JetCacheJdbcKit.getBytes().put(userKey, bytes, token.getTokenExpire(), token.getTimeUnit());
            JetCacheJdbcKit.getToken().put(token.getToken(), userKey, token.getTokenExpire(), token.getTimeUnit());
        }
        return update > 0;
    }

    /**
     * 移除token
     *
     * @param userKey userKey值
     * @return 是否成功
     */
    public boolean del(String userKey) {
        SecurityToken securityToken = getUserKeyToSecurityStorage(userKey);
        String sql = "delete from tb_login_token where user_key = ?";
        int update = jdbcTemplate.update(sql, userKey);
        if (update > 0) {
            JetCacheJdbcKit.clearAll(userKey, securityToken != null ? securityToken.getToken() : null);
        }
        return update > 0;
    }

    /**
     * 修改 Authentication
     *
     * @param userKey        userKey
     * @param authentication authentication
     * @return 是否修改成功
     */
    public boolean updateAuthentication(String userKey, Authentication authentication) {
        String sqlCount = "select count(1) from tb_login_token where user_key = ?";
        if (jdbcTemplate.queryForObject(sqlCount, new Object[]{userKey}, Integer.class) > 0) {
            String sqlUpdate = "update tb_login_token set info = ? where user_key = ?";
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            IoUtil.writeObjects(outputStream, true, authentication);
            byte[] bytes = outputStream.toByteArray();
            int update = jdbcTemplate.update(sqlUpdate, bytes, userKey);
            if (update > 0) {
                JetCacheJdbcKit.updateBytes(userKey, bytes);
            }
            return update > 0;
        }
        if (log.isDebugEnabled()) {
            log.debug("userKey：" + userKey + " non-existent");
        }
        return true;
    }
}
